#![allow(dead_code)]
// #![allow(unused_imports)]

use std::ops::Div;
use std::sync::Arc;

use ray_tracing::{
    vec::*,
    ray::Ray,
    object::{
        hittable::*,
        sphere::Sphere,
        hittable_list::HittableList,
        material::*,
    },
    camera::Camera,
    utils,
};


fn main() {
    
    // Image 
    let aspect_ration:f32 = 16.0 / 9.0;
    let image_width = 400;
    let image_height = (image_width as f32 / aspect_ration ) as i32;
    let samples_per_pixel = 100;
    let max_depth = 50;

    // World
    let mut world = HittableList::new();

    let material_ground = Arc::new(Lambertian::new(Color::new(0.8, 0.8,0.)));
    let material_center = Arc::new(Lambertian::new(Color::new(0.7,0.3,0.3)));
    let material_left = Arc::new(Metal::new(Color::new(0.8,0.8,0.8), 0.3));
    let material_right = Arc::new(Metal::new(Color::new(0.8,0.6, 0.2), 1.0));

    world.add(Sphere::new(Point3::new(0., -100.5, -1.), 100., material_ground.clone()));
    world.add(Sphere::new(Point3::new(0.,0.,-1.0), 0.5, material_center.clone()));
    world.add(Sphere::new(Point3::new(-1.,0.,-1.0), 0.5, material_left.clone()));
    world.add(Sphere::new(Point3::new(1.,0.,-1.0), 0.5, material_right.clone()));

    


    // Camera
    let camera = Camera::default();

    // Render

    println!("P3\n{} {}\n255", image_width,image_height);
    
    for j in (0..image_height).rev() {
        // eprintln!("Scan lines remaining: {}",j);
        for i in 0..image_width {
            let mut pixel_color = Color::default();
            for _ in 0..samples_per_pixel {
                let u = (i as f32 + utils::random_f32()).div((image_width -1) as f32);
                let v = (j as f32 + utils::random_f32()).div((image_height -1) as f32);

                let r = camera.get_ray(u, v);
                pixel_color += ray_color(&r, &world, max_depth);
            }
            write_color(pixel_color, samples_per_pixel );
        }
    }
}


fn ray_color<H>(r: &Ray, world: &H, depth: i32) -> Color
    where H: Hittable  
{
    if depth <= 0 {
        return Color::default();
    }

    let mut rec = HitRecord::default();
    if world.hit(&r, 0.001,f32::INFINITY, &mut rec) {
        // let target = rec.p + random_in_hemisphere(&rec.normal);
        // return 0.5 * ray_color(&Ray::new(rec.p, target - rec.p), world, depth-1);

        let mut scattered = Ray::default();
        let mut attenuation = Color::default();

        match &rec.mat_ptr {
            Some(mat) => {
                if mat.scatter(&r, &rec, &mut attenuation, &mut scattered) {
                    return attenuation * ray_color(&scattered, world, depth - 1)
                } else {
                   return Color::default();
                }
            },
            None => return Color::default()
        }
    }

    let unit_dir = r.direction().unit_vector();
    let t = 0.5 * (unit_dir.y() + 1.0 );
    return (1.0 - t) * Color::new(1.0,1.0, 1.0) + t * Color::new(0.5, 0.7, 1.0)
}

fn hit_sphere(center:&Point3, radius: f32, r: &Ray)-> f32 {
    let oc = *r.origin() - *center;
    let a = r.direction().length_squard();
    let half_b = oc.dot(r.direction());
    let c = oc.length_squard() - radius.powi(2);

    let discrimiant = half_b.powi(2) -  a * c;
    if discrimiant < 0. {
        return -1.0;
    } else {
        return (-half_b - discrimiant.sqrt())/a ;
    }

}